package se.topshelf.golftracker.web.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import se.topshelf.golftracker.core.domain.BaseEntity;
import se.topshelf.golftracker.core.service.BaseService;

import java.util.List;

/**
 * Super class for all Validators of entity domain objects (inheriting from class BaseEntity) .
 * <p/>
 * Controls the algorithm in method validate and delegates to sub class when necessary
 */
public abstract class BaseEntityValidator<T extends BaseEntity> implements Validator {
    public static final String REQUIRED_ERROR_CODE = "required";
    public static final String REQUIRED_ERROR_DEFAULT_MESSAGE = "Field is required.";
    private Class<T> baseEntityClass;

    public BaseEntityValidator(Class<T> baseEntityClass) {
        this.baseEntityClass = baseEntityClass;
    }

    public final boolean supports(Class<?> clazz) {
        return clazz.isAssignableFrom(baseEntityClass);
    }

    @SuppressWarnings({"unchecked"})
    public final void validate(Object target, Errors errors) {
        if (!supports(target.getClass())) {
            throw new IllegalArgumentException("Tried to validate target entity of wrong kind: " + target);
        }
        T entityToCheck = (T) target;

        validateFields(entityToCheck, errors);

        if(! errors.hasErrors()){
           // No need to validate further if fields do not validate
        validateIfNaturalKeyAlreadyTaken(entityToCheck, errors);

        validateBusinessLogic(entityToCheck, errors);
        }

    }


    protected abstract void validateFields(T entityToCheck, Errors errors);

    /**
     * Validates if the natural key of this entity is already persisted (stored in database). E.g if a golf id is the
     * natural key of a golfer, then no two entities can exist persisted at the same time. Only ig the entity to be
     * saved is the same (by synthetic id), it is okay to save it.
     *
     * @param entityToCheck
     * @param errors
     */
    private void validateIfNaturalKeyAlreadyTaken(T entityToCheck, Errors errors) {
        List<T> allSavedEntitiesOfType = getPresentComparableEntities();

        for (T currSavedEntity : allSavedEntitiesOfType) {
            if (entityToCheck.getId() != null && currSavedEntity.getId().equals(entityToCheck.getId())) {
                // Comparing with its already stored instance
                // If the same, then its a save of an already stored entity which is okay
                continue;
            }
            if (isNaturalKeyEqual(currSavedEntity, entityToCheck)) {
                // Now we have a problem since an entity with another id is already stored with the natural key,
                // which is of course is illegal
                doRejectOfNaturalField(errors, entityToCheck);
            }
        }
    }

    protected List<T> getPresentComparableEntities() {
        // Override if necessary
        return getBaseService().findAll(baseEntityClass);
    }

    protected void validateBusinessLogic(T entityToCheck, Errors errors) {
        // Does nothing, override if necessary
    }

    protected abstract BaseService getBaseService();

    protected abstract boolean isNaturalKeyEqual(T savedEntity, T entityToCheck);

    protected abstract void doRejectOfNaturalField(Errors errors, T entityToCheck);


}
